﻿using System;
using System.Reflection;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

namespace MugenInjection.Attributes
{
    /// <summary>
    /// Attribute that indicates where need inject specified service if service can't resolve using default value.
    /// </summary>
    public sealed class OptionalParameterAttribute : ResolverAttribute
    {
        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionalParameterAttribute"/> class.
        /// </summary>
        public OptionalParameterAttribute()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="OptionalParameterAttribute"/> class.
        /// </summary>
        public OptionalParameterAttribute(object defaultValue)
        {
            Value = defaultValue;
        }

        #endregion

        #region Property

        /// <summary>
        /// Gets or sets the default value.
        /// </summary>
        public object Value { get; set; }

        #endregion

        #region Method

        private static object GetDefault(Type type)
        {
#if NETFX_CORE
            if (type.GetTypeInfo().IsValueType)
#else
            if (type.IsValueType)
#endif
                return Activator.CreateInstance(type);

            return null;
        }

        private object GetValue(Type type, MemberInfo memberInfo)
        {
            if (Value == null)
                return GetDefault(type);
            if (!type.IsInstanceOfType(Value))
                throw new InvalidCastException(
                    string.Format(
                        "OptionalParameterAttribute unable to cast object of type {0} into an object of {1}. In {2}.",
                        InjectorUtilsInternal.FormatType(type), InjectorUtilsInternal.FormatType(Value.GetType()),
                        InjectorUtilsInternal.FormatMember(memberInfo, null)));
            return Value;
        }

        #endregion

        /*#region Overrides of ResolverAttribute

        /// <summary>
        /// Resolve instance for the specified request.
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <param name="originalServiceType">Contains the element type for arrays, collections, etc.</param>
        /// <param name="serviceType">Contains information about the type of service.</param>
        /// <param name="service">An instance of the service.</param>
        /// <returns><c>True</c> if service was created; otherwise <c>false</c></returns>
        public override bool ResolveValue(IBindingContext bindingContext, Type originalServiceType, ServiceType serviceType, out object service)
        {
            if (serviceType != ServiceType.Simple || bindingContext.Injector.CanResolve(bindingContext, true, false))
            {
                service = null;
                return false;
            }
            Type type = bindingContext.Service;
            bindingContext.Service = originalServiceType;
            if (bindingContext.Injector.CanResolve(bindingContext, true, false))
            {
                bindingContext.Service = type;
                service = null;
                return false;
            }
            service = GetValue(bindingContext.Service, bindingContext.Member);
            return true;
        }

        #endregion*/

        #region Overrides of ResolverAttribute

        /// <summary>
        /// Determines whether the specified request can be resolved.
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <returns><c>True</c> if the specified service has been resolved; otherwise, <c>false</c>.</returns>
        public override bool CanResolve(IBindingContext bindingContext)
        {
            return !bindingContext.Injector.CanResolve(bindingContext, true, true);
        }

        /// <summary>
        /// Resolves instance for the specified <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>.</param>
        /// <returns>An instance of the service.</returns>
        public override object Resolve(IBindingContext bindingContext)
        {
            return GetValue(bindingContext.Service, bindingContext.Member);
        }

        #endregion
    }
}